#pragma once
#include <iostream>
#include <memory>

#include "Epoller.hpp" // 将Epoll的接口进行封装
#include "nocopy.hpp"
#include "Socket.hpp"
#include "log.hpp"

using namespace std;

const uint16_t defaultport = 8080;
const int maxnum = 128;

class Epoll_Server : nocopy // 不想让这个服务器被拷贝
{
public:
    Epoll_Server(uint16_t port = defaultport) // 列表初始化，将成员变量进行初始化
        : _listensock_ptr(new Sock()), _epoll_ptr(new Epoller()), _port(port)
    {
    }

    void Init()
    {
        // 创建、绑定、监听、日志
        _listensock_ptr->Socket();
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();

        lg(INFO, "创建监听套接字成功，文件描述符:%d", _listensock_ptr->Getsockfd());
    }

    void Accept()
    {
        // accept一个新链接，判断是否获取成功
        string clientip;
        uint16_t clientport = 0;
        int sock = _listensock_ptr->Accept(&clientip, &clientport);
        // 即使获取成功但是不能直接继续读，要做的应该是将新获取的连接设置成内核关心的读事件
        if (sock > 0)
        {
            _epoll_ptr->Epoll_Update(EPOLL_CTL_ADD, sock, EPOLLIN);
            lg(INFO, "得到一个新连接,IP地址和端口号:%s,%d", clientip, clientport);
        }
    }

    void Recver(int fd)
    {
        // read
        char buffer[1024];
        int n = read(fd, buffer, sizeof(buffer));
        if (n > 0)
        {
            // 读取成功
            buffer[n] = 0;
            cout<< "读取成功的字符串: " << buffer << endl;
            
            string echo = "server echo $";
            echo += buffer;
            write(fd,echo.c_str(),sizeof(echo));
        }
        else if (n == 0)
        {
            lg(INFO, "客户端退出,close fd is : %d", fd);
            // 然后不在关心
            _epoll_ptr->Epoll_Update(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            lg(WARNING, "读取错误: fd is : %d", fd);
            _epoll_ptr->Epoll_Update(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }

    // 还有参数，已经就绪的数组和当前数组中的最大个数，等待后事件是连续的
    void Dispatcher(struct epoll_event revs[], int maxnum)
    {
        // 遍历数组
        for (int i = 0; i < maxnum; i++)
        {
            // 拿到当前数组所对应的文件描述符，还有当前文件所关心的事件
            // 根据事件判断是关心的写还是读还是其他
            if (revs[i].events & EPOLLIN) // ？？？为什么
            {
                // 日志：get a new link
                // 如果是关心的读，那么在判断是连接还是普通读取事件，然后调用不同的函数
                if (revs[i].data.fd == _listensock_ptr->Getsockfd())
                {
                    Accept();
                }
                else
                {
                    Recver(revs[i].data.fd);
                }
            }
            else if (revs[i].events & EPOLLOUT)
            {
                // ...
            }
            else
            {
                // ...
            }
        }
    }

    void Start()
    {
        // 将_listensock添加到epoll中 等价于 listensock和他关心的事件，添加到内核epoll模型中rb_tree
        _epoll_ptr->Epoll_Update(EPOLL_CTL_ADD, _listensock_ptr->Getsockfd(), EPOLLIN);
        // 创建epoll_event数组,这个数组是不是就是就绪队列
        struct epoll_event revs[maxnum];
        while (true)
        {
            // 让内核来关心文件描述符所关心的事件，根据返回值判断是否有文件描述符就绪
            int n = _epoll_ptr->Epoll_Wait(revs, maxnum);
            if (n > 0)
            {
                // 有事件就绪就调用分配器函数
                lg(INFO, "有事件就绪, fd is : %d", revs[0].data.fd); // ？？？为什么是0
                Dispatcher(revs, n);
            }
            else if (n == 0)
            {
                lg(INFO, "time out 超时...");
            }
            else
            {
                lg(ERROR, "epll wait 错误");
            }
        }
    }

    ~Epoll_Server()
    {
        // 关闭监听套接字
        _listensock_ptr->Close();
    }

private:
    // 共享指针Sock与Epoller
    std::shared_ptr<Sock> _listensock_ptr;
    std::shared_ptr<Epoller> _epoll_ptr;
    // 端口号
    uint16_t _port;
};